package com.google.code.UtopiaXml;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Class representing xs:complexType. It consists of attributes, simpleContent
 * and one Indicator (for example a Sequence). It supports generating
 * corresponding class and parse*() method.
 *
 * @author Michael Simacek
 */
public class ComplexType implements ClassTemplate {

    private String name;
    private String originalName;
    private SimpleType simpleContent;
    private Indicator indicator;
    private List<Attribute> attrs;

    /**
     * Creates empty complexType with given name.
     *
     * @param name original name of complexType. It will be converted
     * afterwards.
     */
    public ComplexType(String name) {
        this.name = CodeBuilder.toUpperCamelCase(name);
        originalName = name;
        attrs = new ArrayList<>();
    }

    /**
     * Makes a deep copy of itself
     *
     * @param name name of the copy
     * @return depp copy of this ComplexType
     */
    public ComplexType copy(String name) {
        ComplexType ret = new ComplexType(name);
        if (simpleContent != null) {
            ret.simpleContent = simpleContent.copy();
        }
        ret.attrs = new ArrayList<>(attrs);
        ret.indicator = indicator.copy();
        return ret;
    }

    /**
     * Returns the converted name of the class to be generated.
     *
     * @return converted name.
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the original name of type that was present in schema.
     *
     * @return original name
     */
    public String getOriginalName() {
        return originalName;
    }

    /**
     * Sets a simpleContent to this complexType. It will be then accessed by
     * getContent()/setContent() in the generated class.
     *
     * @param simpleContent type of the content
     */
    public void setSimpleContent(SimpleType simpleContent) {
        this.simpleContent = simpleContent;
    }

    /**
     * Sets the content Indicator (for example Sequence) for this type.
     *
     * @param indicator indicator to be added.
     */
    public void setIndicator(Indicator indicator) {
        this.indicator = indicator;
    }

    /**
     * Returns its simple content in order to be able to edit it
     *
     * @return simple content type
     */
    public SimpleType getSimpleContent() {
        return simpleContent;
    }

    /**
     * Returns its indicator in order to be able to edit it
     *
     * @return indicator
     */
    public Indicator getIndicator() {
        return indicator;
    }

    /**
     * Adds one attribute to the type.
     *
     * @param attr attribute to be added
     */
    public void addAttribute(Attribute attr) {
        attrs.add(attr);
    }

    @Override
    public ClassBuilder generateClass(String pkgName) {
        ClassBuilder clb = new ClassBuilder(name, pkgName);
        CodeBuilder cb = clb.getClassContentBuilder();

        //Fields
        for (Property attr : attrs) {
            clb.addProperty(attr);
        }

        if (simpleContent != null) {
            clb.addProperty(new SingleProperty(simpleContent, "content"));
        }

        if (indicator != null) {
            clb.addProperties(indicator.getProperties());
        }

        return clb;
    }

    @Override
    public void generateParseMethod(CodeBuilder cb) {
        cb.appendLine("//begin ComplexType");
        cb.appendLine("private " + name + " parse" + name + "(Node node) {");
        String resName = "new" + name;
        cb.appendLine(name + " " + resName + " = new " + name + "();");

        if (!attrs.isEmpty()) {
            cb.appendLine("if (node.hasAttributes()) {");
            cb.appendLine("Element element = (Element)node;");
            cb.addImport("org.w3c.dom.Element");
            cb.appendLine("String attr;");
            for (Attribute attr : attrs) {
                cb.appendLine("attr = element.getAttribute(\"" + attr.getAttributeName() + "\");");
                cb.appendLine("if (!\"\".equals(attr)) {");
                String resAttrName = attr.getName();
                attr.getType().appendLoaderFromString(cb, "attr", resAttrName);
                cb.appendLine(resName + ".set" + CodeBuilder.toUpperCamelCase(attr.getName()) + "(" + resAttrName + ");");
                cb.appendLine("}");
            }
            cb.appendLine("}");
        }

        if (simpleContent != null) {
            cb.appendLine("Node textNode = node.getFirstChild();");
            cb.appendLine("while (textNode != null && textNode.getNodeType() != Node.TEXT_NODE) {");
            cb.appendLine("textNode = textNode.getNextSibling();");
            cb.appendLine("}");
            cb.appendLine("if (textNode != null) {");
            simpleContent.appendLoaderFromString(cb, "textNode.getNodeValue()", "content");
            cb.appendLine(resName + ".setContent(content);");
            cb.appendLine("}");
        }

        if (indicator != null) {
            cb.appendLine("node = getFirstElement(node);");
            cb.appendLine("if (node != null) {");
            cb.appendLine("String nodeName = node.getNodeName();");
            StringBuilder cond = new StringBuilder();
            for (String match : indicator.getMatchStrings()) {
                cond.append("\"").append(match).append("\"").append(".equals(nodeName) || ");
            }
            if (cond.length() > 3) {
                cond.setLength(cond.length() - 4);
            }
            cb.appendLine("if (" + cond + ") {");
            indicator.generateParserBlock(cb, resName, "node");
            cb.appendLine("}");
            cb.appendLine("}");

        }

        cb.appendLine("return " + resName + ";");
        cb.appendLine("}");
        cb.appendLine("//end ComplexType");
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 71 * hash + Objects.hashCode(this.originalName);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final ComplexType other = (ComplexType) obj;
        if (!Objects.equals(this.originalName, other.originalName)) {
            return false;
        }
        return true;
    }
}
